package com.kfyang.manualgear.generator.declare;

import com.kfyang.manualgear.constant.Constants;
import com.kfyang.manualgear.util.ElementUtils;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.kfyang.manualgear.annotation.crud.Frag;
import com.kfyang.manualgear.annotation.crud.Param;
import com.kfyang.manualgear.constant.TypeNameConstants;
import com.kfyang.manualgear.struct.MethodInfo;
import com.kfyang.manualgear.util.StringUtils;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import java.util.*;
import java.util.regex.Pattern;

import static com.kfyang.manualgear.constant.StatementConstants.*;
import static com.kfyang.manualgear.util.CheckUtils.checkNotNull;

/**
 * 一般方法生成抽象类
 */
public abstract class AbstractGenerator {
    protected MethodSpec.Builder methodBuilder;
    protected final ClassWrapper classWrapper;
    protected final ExecutableElement element;

    protected MethodInfo methodInfo;

    /**
     * 参数别名哈希表
     */
    protected final Map<String, String> paramMap = new HashMap<>();

    /**
     * frag id 到注解的哈希表
     */
    protected final Map<String, MethodInfo.Frag> fragMap = new HashMap<>();

    protected static final Pattern FRAG_DELIMITER_PATTERN = Pattern.compile("[\\[\\]]");
    protected static final Pattern SLOT_PATTERN = Pattern.compile("\\{\\{.*?}}|\\{.*?}");
    protected static final Pattern BRACE_PATTERN = Pattern.compile("\\{(.*?)}");

    public AbstractGenerator(ExecutableElement element, ClassWrapper classWrapper) {
        this.element = element;
        this.classWrapper = classWrapper;

        if (classWrapper.methodInfoMap == null) {
            methodInfo = new MethodInfo();
        } else {
            methodInfo = classWrapper.methodInfoMap.getOrDefault(element.getSimpleName().toString(), new MethodInfo());
        }
    }

    protected String methodName() {
        return element.getSimpleName().toString();
    }

    /**
     * 检查是否满足条件
     *
     * @return 布尔类型
     */
    protected abstract boolean notSupport();

    /**
     * 添加拼接sql的java代码
     */
    protected abstract void addSql();

    /**
     * 封装结果返回
     */
    protected abstract void addReturn();

    public MethodSpec build() {
        if (notSupport()) {
            return null;
        }
        init();
        addParameters();
        copyAnnotations();

        newParameterList();
        addSql();
        addReturn();
        return methodBuilder.build();
    }

    protected void copyAnnotations() {
        ElementUtils.copyAnnotations(element, methodBuilder);
    }

    /**
     * 初始化方法和参数
     */
    protected void init() {
        methodBuilder = MethodSpec.methodBuilder(methodName())
            .addModifiers(Modifier.PUBLIC)
            .returns(TypeName.get(element.getReturnType()));
        initParamMap();
        initFragMap();
    }

    /**
     * 将参数别名填充到哈希表中
     */
    protected void initParamMap() {
        for (VariableElement parameter : element.getParameters()) {
            Param param = parameter.getAnnotation(Param.class);
            String parameterName = parameter.getSimpleName().toString();
            if (param != null) {
                paramMap.put(param.value(), parameterName);
            } else {
                paramMap.put(parameterName, parameterName);
            }
        }
    }

    /**
     * 初始化 frag 哈希表
     */
    protected void initFragMap() {
        if (methodInfo.frags == null) {
            Frag[] frags = element.getAnnotationsByType(Frag.class);
            for (Frag frag : frags) {
                fragMap.put(frag.id(), new MethodInfo.Frag(frag.id(), frag.value(), frag.cond()));
            }
        } else {
            for (MethodInfo.Frag frag : methodInfo.frags) {
                fragMap.put(frag.id, frag);
            }
        }
    }

    /**
     * 复制所有参数
     */
    protected void addParameters() {
        List<ParameterSpec> parameters = element.getParameters().stream()
            .map(ParameterSpec::get)
            .toList();
        methodBuilder.addParameters(parameters);
    }

    /**
     * 新建参数列表
     */
    protected void newParameterList() {
        methodBuilder.addStatement(NEW_PARAMETER_LIST,
            TypeNameConstants.LIST_OBJECT_TYPE, TypeNameConstants.ARRAY_LIST_TYPE);
    }

    /**
     * 拼接sql
     *
     * @param sql sql
     */
    protected void generateSqlAndFrag(String sql) {
        generateSqlAndFrag(sql, new HashSet<>());
    }

    /**
     * 拼接sql
     *
     * @param sql sql
     * @param parentFrags 路径上所有fragId，用于防止循环包含
     */
    private void generateSqlAndFrag(String sql, Set<String> parentFrags) {
        String[] sqlAndFrags = FRAG_DELIMITER_PATTERN.split(sql, -1);
        for (int i = 0; i < sqlAndFrags.length; i++) {
            if (i % 2 == 0) { // 非frag
                String partSql = sqlAndFrags[i];
                if (partSql.isBlank()) {
                    continue;
                }
                generateSqlBlock(partSql);
            } else { // Frag
                String fragId = sqlAndFrags[i];
                if (parentFrags.contains(fragId)) {
                    throw new RuntimeException("frag 不能循环包含");
                }
                MethodInfo.Frag frag = fragMap.get(fragId);
                if (frag == null && classWrapper.globalFragMap != null) {
                    frag = classWrapper.globalFragMap.get(fragId);
                }
                if (frag == null) {
                    System.out.println(element.getSimpleName());
                    throw new RuntimeException("frag 未定义");
                }
                String condStr = BRACE_PATTERN
                    .matcher(frag.cond)
                    .replaceAll(matchResult -> slotToCode(matchResult.group(1)));
                methodBuilder.beginControlFlow(IF_BEGIN, condStr);
                parentFrags.add(fragId);
                generateSqlAndFrag(frag.sql, parentFrags);
                parentFrags.remove(fragId);
                methodBuilder.endControlFlow();
            }
        }
    }

    /**
     * 替换{}和{{}}
     *
     * @param sql 替换后的sql
     */
    private void generateSqlBlock(String sql) {
        String finalSql = SLOT_PATTERN.matcher(sql)
            .replaceAll(matchResult -> {
                String slot = matchResult.group(0);
                slot = slot.substring(1, slot.length() - 1);
                if (slot.startsWith(Constants.LEFT_BRACE) && slot.endsWith(Constants.RIGHT_BRACE)) { // 替换 {{}}
                    String listName = slotToCode(slot.substring(1, slot.length() - 1));
                    methodBuilder.addStatement(PARAMETER_ADD_ALL, listName);
                    return "\").append(inList(%s.size())).append(\"".formatted(listName);
                } else { // 替换{}
                    methodBuilder.addStatement(PARAMETER_ADD, slotToCode(slot));
                    return Constants.PLACE_HOLDER;
                }
            });
        methodBuilder.addStatement("sql.append(\"$L \")", finalSql);
    }

    /**
     * 解析{}和{{}}内的变量
     *
     * @param slot {}和{{}}内的变量名
     * @return 解析后结果
     */
    private String slotToCode(String slot) {
        String[] strings = slot.split(Constants.ESCAPE_DOT);
        String paramName = paramMap.get(strings[0]);
        checkNotNull(paramName, "找不到对应的方法参数");
        StringBuilder stringBuilder = new StringBuilder(paramName);
        for (int i = 1; i < strings.length; i++) {
            String field = strings[i];
            if (field.endsWith(Constants.RIGHT_ROUND_BRACKET) || field.endsWith(Constants.RIGHT_SQUARE_BRACKET) || field.endsWith(Constants.RIGHT_BRACE)) {
                stringBuilder.append(Constants.DOT).append(field);
            } else { // 将字段转换为get方法调用
                stringBuilder.append(Constants.DOT).append(StringUtils.toGetMethod(field))
                    .append(Constants.NO_PARAM_CALL);
            }
        }
        return stringBuilder.toString();
    }
}
